﻿using Newtonsoft.Json;
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Web;

namespace Partner.Utility.WeChat
{
    #region Api
    public static class WeChatApi
    {
        /// <summary>
        /// 拿到微信授权的URL
        /// </summary>
        /// <param name="backUrl">需要回调的地址（起始不带“/”）</param>
        /// <returns></returns>
        public static string GetWeChatAuthorizationLink(string backUrl)
        {
            string url = $"{WeChatLoginConst.WeChatAuthorizationLink.RequestCode}?appid={WeChatLoginConst.OfficialAccountModel.AppID}&redirect_uri={HttpUtility.UrlEncode(WeChatLoginConst.OfficialAccountModel.Scheme + WeChatLoginConst.OfficialAccountModel.URI + "/" + backUrl)}&response_type={WeChatLoginConst.ResponseType.Code}&scope={WeChatLoginConst.Scope.Snsapi_Userinfo}&connect_redirect=1#wechat_redirect";
            return url;
        }
        /// <summary>
        /// 根据微信返回的动态usercode，获取用户关注公众号的openid
        /// </summary>
        /// <param name="userCode"></param>
        /// <returns></returns>
        public static BizResult<Token> RequestToken(string userCode)
        {
            try
            {
                //根据code得到用户openid和token
                string accessTokenUrl = $"{WeChatLoginConst.WeChatAuthorizationLink.RequestToken}?appid={WeChatLoginConst.OfficialAccountModel.AppID}&secret={WeChatLoginConst.OfficialAccountModel.AppSecret}&code={userCode}&grant_type={WeChatLoginConst.GrantType.Authorization_Code}";
                HttpWebRequest requestToken = (HttpWebRequest)WebRequest.Create(accessTokenUrl);
                requestToken.Timeout = 2000;
                requestToken.Method = "GET";
                HttpWebResponse responseToken = (HttpWebResponse)requestToken.GetResponse();
                StreamReader srToken = new StreamReader(responseToken.GetResponseStream(), System.Text.Encoding.UTF8);
                var resultToken = srToken.ReadToEnd();
                //得到access_token和openid
                Token token = JsonConvert.DeserializeObject<Token>(resultToken);
                if (token.openid == null)
                {
                    return new BizResult<Token>() { Success = false, Data = token, Message = JsonConvert.SerializeObject(token) };
                }
                else
                    return new BizResult<Token>() { Success = true, Data = token, Message = JsonConvert.SerializeObject(token) };
            }
            catch (Exception ex)
            {
                return new BizResult<Token>() { Success = false, Message = ex.Message };
            }
        }
        /// <summary>
        /// 拿到唤起微信扫一扫的URL
        /// </summary>
        /// <param name="backUrl">（起始不带“/”）</param>
        /// <returns></returns>
        public static string GetCallScanQrCodeLink(string backUrl)
        {
            string url = $"HTTP://{WeChatLoginConst.OfficialAccountModel.Scan_URI}/wechatlogin/TuneOut?redirect_uri={HttpUtility.UrlEncode(WeChatLoginConst.OfficialAccountModel.Scheme + WeChatLoginConst.OfficialAccountModel.URI + "/" + backUrl)}";
            return url;
        }
        /// <summary>
        /// 拿到可回调的扫码签退二维码字符
        /// </summary>
        /// <param name="backUrl">（起始带“http://”）</param>
        /// <returns></returns>
        public static string GetSignOutQrCodeString(string backUrl)
        {
            var url = WeChatLoginConst.OfficialAccountModel.Scheme + WeChatLoginConst.OfficialAccountModel.Scan_URI + "/WeChatLogin?redirect_uri=" + backUrl;
            return url;
        }
        /// <summary>
        /// 基础支持：获取access_token（暂时没有验证token过期，现在每次都是取最新的，后期可以调整成如果没过期就直接用，100000次/日）
        /// </summary>
        /// <returns></returns>
        public static BizResult<string> GetAccessToken()
        {
            try
            {
                //根据code得到用户openid和token
                string accessTokenUrl = $"{WeChatLoginConst.WeChatAuthorizationLink.RequestBaseToken}?appid={WeChatLoginConst.OfficialAccountModel.AppID}&secret={WeChatLoginConst.OfficialAccountModel.AppSecret}&grant_type={WeChatLoginConst.GrantType.Client_Credential}";
                HttpWebRequest requestToken = (HttpWebRequest)WebRequest.Create(accessTokenUrl);
                requestToken.Timeout = 2000;
                requestToken.Method = "GET";
                HttpWebResponse responseToken = (HttpWebResponse)requestToken.GetResponse();
                StreamReader srToken = new StreamReader(responseToken.GetResponseStream(), System.Text.Encoding.UTF8);
                var resultToken = srToken.ReadToEnd();
                //得到access_token和openid
                TokenBase token = JsonConvert.DeserializeObject<TokenBase>(resultToken);
                if (token.access_token.IsNullEmpty())
                {
                    return new BizResult<string>() { Success = false, Data = token.errcode, Message = JsonConvert.SerializeObject(token) };
                }
                else
                    return new BizResult<string>() { Success = true, Data = token.access_token, Message = JsonConvert.SerializeObject(token) };
            }
            catch (Exception ex)
            {
                return new BizResult<string>() { Success = false, Data = ex.HResult.ToString(), Message = ex.Message };
            }
        }
        /// <summary>
        /// 客服接口-发消息
        /// </summary>
        /// <param name="openid"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public static BizResult<string> SendMsg(string openid, string msg)
        {
            try
            {
                var resultToken = GetAccessToken();
                if (resultToken.Success == false)
                    return new BizResult<string>() { Success = false, Message = "获取access_token失败" };
                //根据code得到用户openid和token
                string sendMsgUrl = $"{WeChatLoginConst.MessageLink.RequestCustomSend}={resultToken.Data}";
                HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(sendMsgUrl);
                httpWebRequest.ContentType = "application/json; encoding=utf-8";
                httpWebRequest.Method = "POST";
                var msgBody = new Message()
                {
                    touser = openid,
                    msgtype = "text",
                    text = new MessageText { content = msg }
                };
                using (StreamWriter streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
                {
                    streamWriter.Write(JsonConvert.SerializeObject(msgBody));
                    streamWriter.Flush();
                    streamWriter.Close();
                    HttpWebResponse httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
                    using (StreamReader streamReader = new StreamReader(httpResponse.GetResponseStream()))
                    {
                        var result = streamReader.ReadToEnd();
                        return new BizResult<string>() { Success = true, ErrorData = result };
                    }
                }
            }
            catch (Exception ex)
            {

            }
            return new BizResult<string>() { Success = false, Message = "消息发送失败" };
        }
        /// <summary>
        /// 从微信拉去用户的基本信息（需要用户关注公众号）
        /// </summary>
        /// <param name="openid"></param>
        /// <returns></returns>
        public static BizResult<UserInfo> GetUserInfoInvalid(string openid)
        {
            try
            {
                var resultToken = GetAccessToken();
                if (resultToken.Success == false)
                    return new BizResult<UserInfo>() { Success = false, Message = "获取access_token失败" };
                //根据code得到用户openid和token
                string url = $"{WeChatLoginConst.UserLink.RequestUserInfo}={resultToken.Data}&openid={openid}&lang=zh_CN";

                HttpWebRequest requestToken = (HttpWebRequest)WebRequest.Create(url);
                requestToken.Timeout = 2000;
                requestToken.Method = "GET";
                HttpWebResponse response = (HttpWebResponse)requestToken.GetResponse();
                StreamReader streamReader = new StreamReader(response.GetResponseStream(), System.Text.Encoding.UTF8);
                var result = streamReader.ReadToEnd();
                //得到access_token和openid
                UserInfo user = JsonConvert.DeserializeObject<UserInfo>(result);
                return new BizResult<UserInfo>() { Success = true, Data = user };

            }
            catch (Exception ex)
            {
                return new BizResult<UserInfo>() { Success = false, Message = ex.Message };
            }
        }
        /// <summary>
        /// 从微信拉去用户的基本信息（无需用户关注公众号）
        /// </summary>
        /// <param name="openid"></param>
        /// <returns></returns>
        public static BizResult<UserInfo> GetUserInfo(Token token)
        {
            try
            {
                if (token.access_token.IsNullEmpty())
                    return new BizResult<UserInfo>() { Success = false, Message = "获取access_token失败" };
                //获取用户信息
                string url = $"{WeChatLoginConst.UserLink.UserInfo}={token.access_token}&openid={token.openid}&lang=zh_CN";

                HttpWebRequest requestToken = (HttpWebRequest)WebRequest.Create(url);
                requestToken.Timeout = 2000;
                requestToken.Method = "GET";
                HttpWebResponse response = (HttpWebResponse)requestToken.GetResponse();
                StreamReader streamReader = new StreamReader(response.GetResponseStream(), System.Text.Encoding.UTF8);
                var result = streamReader.ReadToEnd();
                //得到access_token和openid
                UserInfo user = JsonConvert.DeserializeObject<UserInfo>(result);
                return new BizResult<UserInfo>() { Success = true, Data = user };

            }
            catch (Exception ex)
            {
                return new BizResult<UserInfo>() { Success = false, Message = ex.Message };
            }
        }

        public static BizResult<Token> RefreshAccessToken(string refresh_token)
        {
            try
            {
                var url = $"{WeChatLoginConst.WeChatAuthorizationLink.RequestRefreshToken}?appid={WeChatLoginConst.OfficialAccountModel.AppID}" +
                     $"&grant_type={WeChatLoginConst.GrantType.Refresh_Token}&{WeChatLoginConst.GrantType.Refresh_Token}={refresh_token}";
                HttpWebRequest requestToken = (HttpWebRequest)WebRequest.Create(url);
                requestToken.Timeout = 2000;
                requestToken.Method = "GET";
                HttpWebResponse resp = (HttpWebResponse)requestToken.GetResponse();
                StreamReader srToken = new StreamReader(resp.GetResponseStream(), Encoding.UTF8);
                var stringToken = srToken.ReadToEnd();
                Token token = JsonConvert.DeserializeObject<Token>(stringToken);
                if (!string.IsNullOrWhiteSpace(token.errcode))
                    return new BizResult<Token> { Success = false, Data = token, Message = JsonConvert.SerializeObject(token) };
                else
                    return new BizResult<Token> { Success = true, Data = token, Message = JsonConvert.SerializeObject(token) };
            }
            catch (Exception ex)
            {
                return new BizResult<Token>() { Success = false, Message = ex.Message };
            }
        }
    }
    #endregion

    #region Const
    public static class WeChatLoginConst
    {
        /// <summary>
        /// 授权
        /// </summary>
        public static class WeChatAuthorizationLink
        {
            /// <summary>
            /// 请求code
            /// </summary>
            public const string RequestCode = "https://open.weixin.qq.com/connect/oauth2/authorize";

            /// <summary>
            /// 请求Token
            /// </summary>
            public const string RequestToken = "https://api.weixin.qq.com/sns/oauth2/access_token";

            /// <summary>
            /// 刷新Token
            /// </summary>
            public const string RequestRefreshToken = "https://api.weixin.qq.com/sns/oauth2/refresh_token";

            /// <summary>
            /// 请求userinfo
            /// </summary>
            public const string RequestUserInfo = "https://api.weixin.qq.com/sns/userinfo";

            /// <summary>
            /// 请求Ticket
            /// </summary>
            public const string RequestJsAPITicket = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";

            /// <summary>
            /// 请求基础支持的Token，用于调用接口
            /// </summary>
            public const string RequestBaseToken = "https://api.weixin.qq.com/cgi-bin/token";
        }
        /// <summary>
        /// 客服
        /// </summary>
        public static class MessageLink
        {
            public const string RequestCustomSend = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token";
        }
        /// <summary>
        /// 用户
        /// </summary>
        public static class UserLink
        {
            /// <summary>
            /// 拉取用户信息（需要用户关注公众号才行）
            /// </summary>
            public const string RequestUserInfo = "https://api.weixin.qq.com/cgi-bin/user/info?access_token";
            /// <summary>
            /// 拉取用户信息无需关注公众号（只需授权过）
            /// </summary>
            public const string UserInfo = "https://api.weixin.qq.com/sns/userinfo?access_token";

        }
        /// <summary>
        /// 公众号信息
        /// </summary>
        public static OfficialAccountModel OfficialAccountModel { get; set; }
        /// <summary>
        /// token请求的类型
        /// </summary>
        public static class GrantType
        {
            /// <summary>
            /// 请求token
            /// </summary>
            public const string Authorization_Code = "authorization_code";

            /// <summary>
            /// 刷新token
            /// </summary>
            public const string Refresh_Token = "refresh_token";

            /// <summary>
            /// 请求基础支持的Token
            /// </summary>
            public const string Client_Credential = "client_credential";
        }

        /// <summary>
        /// 拉取的用户信息的语言
        /// </summary>
        public static class Language
        {
            public const string ZH_CN = "zh_CN";
            public const string ZH_TW = "zh_TW";
            public const string EN = "en";
        }

        /// <summary>
        /// code请求的错误代码
        /// </summary>
        public static class RequestCodeErrCode
        {
            /* 10003	redirect_uri域名与后台配置不一致
             * 10004	此公众号被封禁
             * 10005	此公众号并没有这些scope的权限
             * 10006	必须关注此测试号
             * 10009	操作太频繁了，请稍后重试
             * 10010	scope不能为空
             * 10011	redirect_uri不能为空
             * 10012	appid不能为空
             * 10013	state不能为空
             * 10015	公众号未授权第三方平台，请检查授权状态
             * 10016	不支持微信开放平台的Appid，请使用公众号Appid
             */
        }

        /// <summary>
        /// 请求的返回类型
        /// </summary>
        public static class ResponseType
        {
            /// <summary>
            /// 获取code
            /// </summary>
            public const string Code = "code";
        }

        /// <summary>
        /// 应用授权作用域
        /// </summary>
        public static class Scope
        {
            /// <summary>
            /// 不弹出授权页面，直接跳转，只能获取用户openid
            /// </summary>
            public const string Snsapi_Base = "snsapi_base";

            /// <summary>
            /// 弹出授权页面，可通过openid拿到昵称、性别、所在地。并且， 即使在未关注的情况下，只要用户授权，也能获取其信息
            /// </summary>
            public const string Snsapi_Userinfo = "snsapi_userinfo";
        }

        /// <summary>
        /// 请求类型
        /// </summary>
        public static class Type
        {
            /// <summary>
            /// 请求jsapi_ticket
            /// </summary>
            public const string JsAPI = "jsapi";
        }

        public enum SexEnum
        {
            未知 = 0,
            男 = 1,
            女 = 2,
        }
    }
    #endregion

    #region model
    /// <summary>
    /// 公众号信息
    /// </summary>
    public class OfficialAccountModel
    {
        /// <summary>
        /// 微信回调地址
        /// </summary>
        public string RedirectUri { get; set; }
        /// <summary>
        /// 微信扫一扫回调地址
        /// </summary>
        public string ScanRedirectUri { get; set; }
        /// <summary>
        /// 应用id
        /// </summary>
        public string AppID { get; set; }

        /// <summary>
        /// 开发者密码
        /// </summary>
        public string AppSecret { get; set; }

        /// 扫一扫服务器域名（微信扫一扫服务器域名专用）
        /// </summary>
        public string Scan_URI { get; set; }

        /// <summary>
        /// 服务器域名（微信授权登录专用）
        /// </summary>
        public string URI { get; set; }
        /// <summary>
        /// 内部人员端域名（扫码签退回调专用）
        /// </summary>
        public string URL_Insider { get; set; }

        /// <summary>
        /// 服务器协议http:// or https://
        /// </summary>
        public string Scheme { get; set; }
    }
    /// <summary>
    /// Token
    /// </summary>
    public class Token : WeChatReturn
    {
        /// <summary>
        /// 网页授权接口调用凭证,注意：此access_token与基础支持的access_token不同
        /// </summary>
        private string _access_token;

        /// <summary>
        /// 创建时间
        /// </summary>
        public DateTime CreateTime { get; set; } = DateTime.Now;

        /// <summary>
        /// 刷新token创建时间
        /// </summary>
        public DateTime RefreshTokenCreateTime { get; set; } = DateTime.Now;

        /// <summary>
        /// 读取token，如果超时重新加载
        /// </summary>
        public string access_token
        {
            get
            {
                //由于access_token拥有较短的有效期，当access_token超时后，可以使用refresh_token进行刷新，refresh_token有效期为30天，当refresh_token失效之后，需要用户重新授权。
                if (CreateTime.AddSeconds(expires_in) < DateTime.Now)
                    _access_token = refresh_token;
                //TODO:未考虑超时
                return _access_token;
            }
            set
            {
                _access_token = value;
            }
        }

        /// <summary>
        /// access_token接口调用凭证超时时间，单位（秒）
        /// </summary>
        public int expires_in { get; set; }

        /// <summary>
        /// 用户刷新access_token
        /// </summary>
        public string refresh_token { get; set; }

        /// <summary>
        /// 用户唯一标识，请注意，在未关注公众号时，用户访问公众号的网页，也会产生一个用户和公众号唯一的OpenID
        /// </summary>
        public string openid { get; set; }

        /// <summary>
        /// 用户授权的作用域，使用逗号（,）分隔
        /// </summary>
        public string scope { get; set; }
    }
    /// <summary>
    /// Token
    /// </summary>
    public class TokenBase : WeChatReturn
    {
        /// <summary>
        /// 基础支持的access_token
        /// </summary>
        private string _access_token;

        /// <summary>
        /// 创建时间
        /// </summary>
        public DateTime CreateTime { get; set; } = DateTime.Now;

        /// <summary>
        /// 刷新token创建时间
        /// </summary>
        public DateTime RefreshTokenCreateTime { get; set; } = DateTime.Now;

        /// <summary>
        /// 读取token，如果超时重新加载
        /// </summary>
        public string access_token
        {
            get
            {
                //TODO:未考虑超时
                return _access_token;
            }
            set
            {
                _access_token = value;
            }
        }
        /// <summary>
        /// access_token接口调用凭证超时时间，单位（秒）
        /// </summary>
        public int expires_in { get; set; }
    }
    /// <summary>
    /// 用户code
    /// </summary>
    public class UserCode : WeChatReturn
    {
        private string _code;
        private DateTime createTime;

        public string code
        {
            get
            {
                return _code;
            }
            set
            {
                _code = value;
                createTime = DateTime.Now;
            }
        }

        //public string openid { get; set; }
        public string state { get; set; }
        public int tid { get; set; }
    }
    public class UserInfo : WeChatReturn
    {
        /// <summary>
        /// 用户是否订阅该公众号标识，值为0时，代表此用户没有关注该公众号，拉取不到其余信息
        /// </summary>
        public string subscribe { get; set; }
        /// <summary>
        /// 用户的唯一标识
        /// </summary>
        public string openid { get; set; }

        /// <summary>
        /// 用户昵称
        /// </summary>
        public string nickname { get; set; }

        /// <summary>
        /// 用户的性别，值为1时是男性，值为2时是女性，值为0时是未知
        /// </summary>
        public WeChatLoginConst.SexEnum sex { get; set; }

        /// <summary>
        /// 用户个人资料填写的省份
        /// </summary>
        public string province { get; set; }

        /// <summary>
        /// 普通用户个人资料填写的城市
        /// </summary>
        public string city { get; set; }

        /// <summary>
        /// 国家，如中国为CN
        /// </summary>
        public string country { get; set; }

        /// <summary>
        /// 用户头像，最后一个数值代表正方形头像大小（有0、46、64、96、132数值可选，0代表640*640正方形头像），用户没有头像时该项为空。若用户更换头像，原有头像URL将失效。
        /// </summary>
        public string headimgurl { get; set; }

        /// <summary>
        /// 用户特权信息，json 数组，如微信沃卡用户为（chinaunicom）
        /// </summary>
        public string[] privilege { get; set; }
        /// <summary>
        /// 用户关注时间，为时间戳。如果用户曾多次关注，则取最后关注时间
        /// </summary>
        public string subscribe_time { get; set; }

        /// <summary>
        /// 只有在用户将公众号绑定到微信开放平台帐号后，才会出现该字段。
        /// </summary>
        public string unionid { get; set; }
    }
    public class Message
    {
        /// <summary>
        /// OPENID
        /// </summary>
        public string touser { get; set; }
        /// <summary>
        /// text,image,voice,video
        /// </summary>
        public string msgtype { get; set; }

        public MessageText text { get; set; }
        ///// <summary>
        ///// 
        ///// </summary>
        //public class text
        //{
        //    public string content { get; set; }
        //}
        ///// <summary>
        ///// 
        ///// </summary>
        //public class voice
        //{
        //    public string media_id { get; set; }
        //}
        //其他...见开发文档
    }
    public class MessageText
    {
        public string content { get; set; }
    }
    public class WeChatReturn
    {
        public string errcode { get; set; }
        public string errmsg { get; set; }
    }
    #endregion
}
